#include "PrimitiveSample.h"
#include "Application.h"
#include "utils.h"
#include "Utility.h"
#include <thread>
namespace RayTracer
{
	void PrimitiveSample::Init()
	{
		mImageParam = ImageParam(800, 500, "TrianglePrimitive.png");
		lookfrom = vec3(3.0,2.0,1.0);
		lookat = vec3(0.0,0.0,-1.0);
		fov = 60;
		upColor = vec3(0.5, 0.7, 1.0);
		downColor = vec3(1.0);
		groundColor = vec3(0.8);
		triangleColor = vec3(0.1,0.5,0.8);
		boxColor = vec3(1.0);
		diskColor = vec3(1.0,0.0,0.0);
		rectColor = vec3(1.0,1.0,0.0);
		isGenerate = false;
		curIndex = 0;

		floorMat = std::make_shared<Lambertian>(groundColor);
		triangleMat = std::make_shared<Lambertian>(triangleColor);
		rectMat = std::make_shared<Metal>(rectColor, 0.3f);
		diskMat = std::make_shared<Lambertian>(diskColor);
		boxMat = std::make_shared<Metal>(boxColor, 0.1f);

		floor = std::make_shared<Sphere>(vec3(0, -100.5, -1), 100, floorMat);
		triangle = std::make_shared<Triangle>(vec3(-1.0, -0.2, 1.0), vec3(-0.5, -0.2, 0.0), vec3(0.0, -0.2, 0.5), triangleMat);
		rect = std::make_shared<XY_Rect>(-0.5, 0.5, -0.5, 0.5, -1.5, rectMat);
		disk = std::make_shared<Disk>(vec3(1.5,-0.2, -1.0), vec3(0.0,1.0, 0), 0.5, diskMat);
		box = std::make_shared<Box>(vec3(0.0, 0.0, -1.0), vec3(0.5, 0.5, -0.5), boxMat);
		world.add(floor);
		world.add(triangle);
		world.add(rect);
		world.add(disk);
		world.add(box);
	}

	void PrimitiveSample::Render()
	{
		

		int width = mImageParam.mWidth;
		int height = mImageParam.mHeight;
		camera = Camera(lookfrom, lookat, fov);

		uint8_t* pixels = new uint8_t[width * height * 3];
		int index = 0;
		for (int j = height - 1; j >= 0; j--) {
			for (int i = 0; i < width; ++i) {
				vec3 color = vec3(0.0f);
				for (int s = 0; s < samples_per_pixel; s++)
				{
					auto u = (i + Random01()) / (width - 1);
					auto v = (j + Random01()) / (height - 1);
					Ray r = camera.GetRay(u, v);
					color += RayColor(r, world, maxDepth);
				}
				auto scale = 1.0 / samples_per_pixel;
				color *= scale;

				GammaCorrect(color);
				pixels[index++] = static_cast<uint8_t>(256 * clamp(color.r, 0.0, 0.999));
				pixels[index++] = static_cast<uint8_t>(256 * clamp(color.g, 0.0, 0.999));
				pixels[index++] = static_cast<uint8_t>(256 * clamp(color.b, 0.0, 0.999));
				curIndex++;
			}
		}
		Utils::SaveImage(("Images/" + mImageParam.mName).c_str(), width, height, 3, pixels);
		isGenerate = true;
	}

	void PrimitiveSample::ImGui()
	{
		ImGui::Text(u8"Primitive");
		ImGui::Checkbox(u8"Normal", &isNormal);
		if (isNormal)
		{
			samples_per_pixel = 1;
		}
		else
		{
			ImGui::SliderInt(u8"Spp", &samples_per_pixel, 1, 500);
		}
		ImGui::SliderInt(u8"MaxDepth", &maxDepth, 1, 50);

		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Sky Color"))
		{
			ImGui::ColorEdit3(u8"High", &upColor[0]);
			ImGui::ColorEdit3(u8"Low", &downColor[0]);
		}
		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Camera"))
		{
			ImGui::InputFloat3(u8"Camera Position", &lookfrom[0]);
			ImGui::SliderFloat("Fov", &fov,20.0,180.0);
		}
		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Floor Sphere"))
		{
			ImGui::ColorEdit3(u8"Floor Color",&groundColor[0]);
			ImGui::InputFloat3(u8"Floor Position", &floor->center[0]);
			ImGui::SliderFloat(u8"Floor Radius", &floor->radius, 0.0f, 100.0f);
		}
		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Triangle Left"))
		{
			ImGui::ColorEdit3(u8"TriangleL Color", &triangleColor[0]);
			ImGui::InputFloat3(u8"TriangleL P1", &triangle->P1()[0]);
			ImGui::InputFloat3(u8"TriangleL P2", &triangle->P2()[0]);
			ImGui::InputFloat3(u8"TriangleL P3", &triangle->P3()[0]);
		}
		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Rect"))
		{
			ImGui::ColorEdit3(u8"Rect Color", &rectColor[0]);
			ImGui::SliderFloat(u8"Roughness", &rectMat->GetFuzz(),0.0,1.0);
		}
		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Disk"))
		{
			ImGui::ColorEdit3(u8"Disk Color", &diskColor[0]);
			ImGui::InputFloat3(u8"Disk Position", &disk->GetPosition()[0]);
			ImGui::InputFloat3(u8"Disk Normal", &disk->GetNormal()[0]);
			ImGui::SliderFloat(u8"Disk Radius", &disk->GetRadius(), 0.0, 1.0);
		}

		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Box"))
		{
			ImGui::ColorEdit3(u8"Box Color", &boxColor[0]);
		}
		ImGui::Separator();
		auto w = (float)mImageParam.mWidth;
		auto h = (float)mImageParam.mHeight;
		auto progress = ((float)curIndex / (w * h));
		ImGui::ProgressBar(progress);
		if (ImGui::Button(u8"Generate")) {
			std::thread t = std::thread([=]() { Render(); });
			t.detach();
		}
		if (isGenerate) {
			auto mTexHandle = Utils::LoadTexture(("Images/" + mImageParam.mName).c_str());
			Application::GetInstance()->SetTexHandle(mTexHandle);
			isGenerate = false;
			curIndex = 0;
		}

	}

	void PrimitiveSample::ShutDown()
	{
		mTexHandle = 0;
		Application::GetInstance()->SetTexHandle(mTexHandle);
	}

	vec3 PrimitiveSample::RayColor(const Ray & r, const Hitable& world, int depth)
	{
		HitRecord rec;
		if (depth <= 0) return vec3(0.0f);
		if (world.Hit(r, 0.001, infinity, rec))
		{
			vec3 res;
			if (isNormal)
			{
				res = 0.5f * (rec.normal + vec3(1.0f));
			}
			else
			{
				Ray scattered;
				vec3 attenuation;
				if (rec.matPtr->Scatter(r, rec, attenuation, scattered))
					return attenuation * RayColor(scattered, world, depth - 1);
				return vec3(1.0f);
			}
			return res;
		}

		vec3 unit_direction = glm::normalize(r.direction());
		auto t = 0.5f*(unit_direction.y + 1.0f);
		return (1.0f - t)*downColor + t * upColor;
	}
}